package com.studyv3.framework.lock;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * @description 分布式锁切面
 * @date 2023/9/28
 */
@Aspect
@ConditionalOnClass(DistributedLock.class)
@Component
@RequiredArgsConstructor
@Slf4j
public class DistributedLockAop {

    private final RedissonClient redissonClient;

    @Pointcut("@annotation(com.studyv3.framework.lock.DistributedLock)")
    public void pointCut() {}

    @Around("pointCut() && @annotation(lockAnno))")
    public Object handle(ProceedingJoinPoint joinPoint, DistributedLock lockAnno) throws Throwable {
        String lockKey = bindLockKey(joinPoint, lockAnno);
        RLock lock = redissonClient.getLock(lockKey);
        boolean flag = RedissonUtil.tryLockNoThrow(lock, lockAnno.waitTimeInMill(), lockAnno.leaseTimeInMill(), TimeUnit.MILLISECONDS);
        if (!flag) {
            log.error("分布式锁[{}]获取失败", lockAnno.lockKey());
            throw new IllegalStateException(lockAnno.errMsg());
        }
        try {
            return joinPoint.proceed();
        } finally {
            RedissonUtil.unLockNoThrow(lock);
        }
    }

    /** 构建分布式锁Key */
    private String bindLockKey(ProceedingJoinPoint joinPoint, DistributedLock lockAnno) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Annotation[][] annoArr = methodSignature.getMethod().getParameterAnnotations();
        List<Boolean> flagList = new ArrayList<>(4);

        for (Annotation[] annotations : annoArr) {
            if (annotations == null || annotations.length == 0) {
                continue;
            }
            boolean find = Stream.of(annotations).filter(item -> LockKey.class.equals(item.annotationType())).findFirst().isPresent();
            flagList.add(find);
        }
        int flagSize = flagList.size();
        if (flagSize < 1) {
            return lockAnno.lockKey();
        }

        StringBuilder lockKeyBuilder = new StringBuilder(lockAnno.lockKey());
        Object[] paramValList = joinPoint.getArgs();
        for (int i = 0; i < paramValList.length; i++) {
            if (i >= flagSize) {
                break;
            }
            if (!flagList.get(i).booleanValue()) {
                continue;
            }
            lockKeyBuilder.append(":")
                    .append(paramValList[i]);
        }
        return lockKeyBuilder.toString();
    }
}
